/** @format */

import { EventEmitter } from 'events';
import { inject, injectable } from 'inversify';
import { Browser, launch, Page } from 'puppeteer';
import { ArrayQueue } from './array.queue.service';
import { ILogger, logger } from '../utils/web-logger.util'
import { ConsoleHandler, RequestFailedHandler, RequestHandler } from './puppeteer-components'
import { TYPES } from '../inversify.config.report';
export interface PagePool {
  /**
   * init the pool
   */
  init(): void;

  /**
   * get a page instance
   *
   * @return
   */
  getPage(): Promise<Page>;

  /**
   * release a
   *
   * @param connection
   */
  releasePage(page: Page): Promise<void>;

  /**
   * distroy the page pool
   */
  destroy(): void;
}
@injectable()
export class PagePoolImpl implements PagePool {
  idleConnectPool: ArrayQueue<Page> = new ArrayQueue<Page>(1);
  busyConnectPool: ArrayQueue<Page> = new ArrayQueue<Page>(1);
  private readonly subject: EventEmitter = new EventEmitter();
  browsers: Array<Browser>;

  constructor(@inject(TYPES.ILogger) private logger: ILogger) {
    this.init();
  }

  init(): void {
    this.idleConnectPool = new ArrayQueue<Page>(0);
    this.busyConnectPool = new ArrayQueue<Page>(0);
    this.browsers = new Array<Browser>(0);
  }

  async getPage(): Promise<Page> {
    const page = this.idleConnectPool.pop();
    if (page) {
      const pages = await page.browser().pages()
      logger.info(`got browser page ${pages.indexOf(page)} to generate proposal!`)
      this.busyConnectPool.push(page);
      return page;
    }
    return new Promise(async (resolve, reject) => {
      logger.debug('try to get browser page to generate proposal for another time!')
      this.subject.once('released', () => {
        resolve(this.getPage());
      });
    });
  }

  async releasePage(page: Page): Promise<void> {
    if (page) {
      this.busyConnectPool.pop();
      if (page.isClosed()) {
        logger.info('releasing page closed, release another browser page to generate proposal!')
        page = await this.createAndInitPage(page.browser())
      }
      this.idleConnectPool.push(page);
      this.subject.emit('released');
      logger.info(`released browser page ${(await page.browser().pages()).indexOf(page)} to generate proposal!`);
      return;
    }
    throw new Error('unable to release a undefined page.')
  }

  async destroy(): Promise<void[]> {
    const browsers = this.browsers;
    this.idleConnectPool = new ArrayQueue<Page>(0);
    this.busyConnectPool = new ArrayQueue<Page>(0);
    this.browsers = new Array<Browser>(0);
    return Promise.all(browsers.map(browser => browser.close()));
  }
  async initBrowsers(browserPath: string | undefined, counts: number) {
    while (counts > 0) {
      this.browsers.push(
        await launch({
          executablePath: browserPath,
          headless: process.env.PUPPETEER_VISABLE != '1',
          args: [
            '--disable-web-security',
            '--disable-dev-shm-usage',
            '--no-sandbox',
            '--disable-setuid-sandbox',
            '--disable-accelerated-2d-canvas',
            '--no-first-run',
            '--no-zygote',
            // Debian will crash while enable single process flag
            // '--single-process',
            '--disable-gpu',
          ],
          devtools: process.env.PUPPETEER_VISABLE == '1',
        }),
      );
      counts--;
    }
  }

  private url: string;
  createPages(size: number, url: string): Promise<void[]> {
    this.url = url;
    return Promise.all(
      this.browsers.map(async browser => {
        let tempSize = size;
        while (tempSize > 0) {
          this.idleConnectPool.push(await this.createAndInitPage(browser));
          tempSize--;
          this.logger.debug(tempSize + ':' + this.idleConnectPool.size());
        }
      }),
    );
  }
  async createAndInitPage(browser: Browser): Promise<Page> {
    const page = await browser.newPage();
    const response = await page.goto(this.url);
    if (response == null || (response.status() !== 200 && response.status() !== 304)) {
      throw new Error('unable to load the generation html\n' + (response ? response.statusText() : 'no response'));
    }
    // await page.setRequestInterception(true);
    page.on('console', ConsoleHandler);
    // page.on('requestfailed', RequestFailedHandler);
    // eslint-disable-next-line @typescript-eslint/no-misused-promises
    // page.on('request', RequestHandler);
    return page;
  }
}
